package edu.hawaii.nearestneighbor;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import edu.hawaii.nearestneighbor.classifications.Classification;
import edu.hawaii.nearestneighbor.classifications.ConfClassification;
import edu.hawaii.nearestneighbor.classifiers.CamClassifier;
import edu.hawaii.nearestneighbor.classifiers.KNNClassifier;
import edu.hawaii.nearestneighbor.classifiers.StatConfClassifier;
import edu.hawaii.nearestneighbor.util.DataPoint;
import edu.hawaii.nearestneighbor.util.Parameters;

/**
 * Driver program for running Nearest Neighbor. <br>
 * Invoke with "java -jar NearestNeighbor.jar" from the classes/jar directory.
 * 
 * @author Robert Puckett
 */
public class Main {

  /**
   * Private constructor.
   */
  private Main() {
    // empty
  }
  
  private static int DEBUG = 0;

  /**
   * Main method.
   * @param args command line arguments.
   */
  public static void main(String[] args) {

    System.out.println("Nearest Neighbor Classification Algorithm");
    System.out.println("by Robert Puckett");
    List<Result> results = new ArrayList<Result>();
    List<String> filenames = new ArrayList<String>();
    List<Integer> filefeatures = new ArrayList<Integer>();
    
    if (args.length > 0) {
      DEBUG = Integer.parseInt(args[0]);
    }

    String fileroot = "../../data/";
    // if (args.length == 1) {
    
    for (int z = 2; z <=8; z++) {
      
    String filename = "gauss_" + z + "D.dat";
    filenames.add(filename);
    
    int numFeatures = 0;
    int numClasses = 0;
    int numData = 0;
    int kCam = 0;
    int kNN = 0;
    
    List<Double> features = new ArrayList<Double>();
    List<DataPoint> trainingData = new ArrayList<DataPoint>();
    List<DataPoint> testData = new ArrayList<DataPoint>();

    try {
      // FileInputStream fstream = new FileInputStream(args[0]);
      FileInputStream fstream = new FileInputStream(fileroot+filename);
      // DataInputStream in = new DataInputStream(fstream);
      Scanner in = new Scanner(fstream);
      numFeatures = in.nextInt();
      filefeatures.add(numFeatures);
      numClasses = in.nextInt();
      numData = in.nextInt();
      kNN = in.nextInt();
      kCam = in.nextInt();

      System.out.println();
      System.out.println("File: " + filename);
      // import prototypes into List
      System.out.println("Importing Training Data...");
      for (int i = 0; i < numData / 2; i++) {
        for (int j = 0; j < numFeatures; j++) {
          features.add(in.nextDouble());
        }
        trainingData.add(new DataPoint(new ArrayList<Double>(features), in.nextInt()));
        features.clear();
        if ((i+1) % 500 == 0) {
          System.out.println("Progress: " + (i+1));
        }
      }
      System.out.println("Training Data Import Done.");
      System.out.println("Importing Test Data...");
      // import test data into List
      for (int i = 0; i < numData / 2; i++) {

        for (int j = 0; j < numFeatures; j++) {
          features.add(in.nextDouble());
        }
        testData.add(new DataPoint(new ArrayList<Double>(features), in.nextInt()));
        features.clear();
        if ((i+1) % 500 == 0) {
          System.out.println("Progress: " + (i+1));
        }

      }
      System.out.println("Test Data Import done.");
      in.close();
    }
    catch (Exception e) {
      System.err.println("File input error");
    }

    // } else System.out.println("Invalid parameters");

    if (DEBUG == 3) {
      testData = trainingData;
    }


   
    System.out.println();
    

    
    results.add(test1(trainingData, testData, kNN));
    results.add(test2(trainingData, testData, kCam));
    results.add(test3(trainingData, testData, kNN, .95));
    results.add(test4(trainingData, testData, kCam, .95));
    

    }

    System.out.println("File         #Feat   kNN   k    camNN k   statConf k_av conf_av   Cam/SCT  k_av");

    for (int i = 0; i<filenames.size() ; i++) {
     
    System.out.print(filenames.get(i) + " " + filefeatures.get(i) + "       ");
    for (int j=i*4; j<((i*4)+4); j++ ) {
      System.out.print(results.get(j).toString() + "   ");
    }
    System.out.println();
    }
    
  }

  
  public static Result test1(List<DataPoint> trainingData, List<DataPoint> testData, Integer k) {
    Result results = new Result();

    System.out.println();
    System.out.println("k-NN Classification with k=" + k + ":");
    int correct = 0;
    int error = 0;

    Classification result = null;
    for (DataPoint c : testData) {
      result = KNNClassifier.classify(trainingData, c, k);
      // System.out.println(result.state + " ," + c.state);
      if (DEBUG == 1) {
        System.out.println(result.state + "  ," + c.state);
      }
      if (result.state == c.state) {
        correct++;
      }
      else {
        error++;
      }
      if (((error + correct) % 100) == 0) {
        System.out.println("Progress: " + (error + correct));
      }
    }
    System.out.println("k-NN classification done.");
    System.out.println((double) error / (double) testData.size());
    results.percentError = (double) error / (double) testData.size();
    results.k = k;
    
    return results;
  }

  public static Result test2(List<DataPoint> trainingData, List<DataPoint> testData, Integer k) {
    Result results = new Result();
    System.out.println("Cam Classification:");
    System.out.println("Building Parameters...");
    List<Parameters> A = CamClassifier.trainParameters(trainingData, k);
    System.out.println("Building Parameters Done.");

    if (DEBUG == 2) {
      for (Parameters c : A) {
        System.out.println(c.toString());
      }
    }

    int correct = 0;
    int error = 0;
    Classification result = null;
    System.out.println("Classifying test data...");
    for (DataPoint c : testData) {
      result = CamClassifier.classify(A, trainingData, c, 1);
      if (DEBUG == 1) {
        System.out.println(result.state + "  ," + c.state);
      }
      if (result.state == c.state) {
        correct++;
      }
      else {
        error++;
      }
      if (((error + correct) % 100) == 0) {
        System.out.println("Progress: " + (error + correct));
      }
    }
    System.out.println("Classifying test data done.");
    System.out.println((double) error / (double) testData.size());
    
    results.percentError = (double) error / (double) testData.size();
    results.k = k;
    
    return results; 
  }

  public static Result test3(List<DataPoint> trainingData, List<DataPoint> testData, Integer k, Double threshold) {
    Result results = new Result();

    System.out.println();
    System.out.println("Statistical Confidence Classification with k=" + k + ":");
    int correct = 0;
    int error = 0;

    ConfClassification result2 = null;
    double kAverage = 0.0;
    double confAverage = 0.0;
    
    for (DataPoint c : testData) {
      result2 = StatConfClassifier.classifyWithConfidence(trainingData, c, threshold, k);
      // System.out.println(result.state + " ," + c.state);
      if (DEBUG == 1) {
        System.out.println(result2.state + "  ," + c.state + " k=" + result2.k + "  c=" + result2.confidence);
      }
      if (result2.state == c.state) {
        correct++;
      }
      else {
        error++;
      }
      if (((error + correct) % 100) == 0) {

        System.out.println("Progress: " + (error + correct) + "  Error Rate: " + (double)(error)/(double)(error+correct));
      }
      kAverage = kAverage + result2.k;
      confAverage = confAverage + result2.confidence;
    }
    kAverage = kAverage / testData.size();
    confAverage = confAverage /testData.size();
    
    System.out.println("StatConf classification done.");
    System.out.println((double) error / (double) testData.size());

    
    results.percentError = (double) error / (double) testData.size();
    results.k = (int)kAverage;
    results.confidence = confAverage; 
    
    return results; 
  }

  public static Result test4(List<DataPoint> trainingData, List<DataPoint> testData, Integer k, double threshold) {
    Result results = new Result();
    
    System.out.println();
    System.out.println("Cam Classification with StatConf Training:");
    System.out.println("Building Parameters...");
    List<Parameters> A = CamClassifier.trainParameters(trainingData, k, threshold);
    System.out.println("Building Parameters Done.");

    if (DEBUG == 2) {
      for (Parameters c : A) {
        System.out.println(c.toString());
      }
    }

    int correct = 0;
    int error = 0;
    double kAverage = 0.0;
    
    Classification result = null;
    System.out.println("Classifying test data...");
    for (DataPoint c : testData) {
      result = CamClassifier.classify(A, trainingData, c, 1);
      if (DEBUG == 1) {
        System.out.println(result.state + "  ," + c.state + " k=" + result.k);
      }
      if (result.state == c.state) {
        correct++;
      }
      else {
        error++;
      }
      if (((error + correct) % 100) == 0) {
        System.out.println("Progress: " + (error + correct));
      }
      kAverage = kAverage + result.k;
        
    }
    kAverage = kAverage / testData.size();
    System.out.println("Classifying test data done.");
    System.out.println((double) error / (double) testData.size());
    
    results.percentError = (double) error / (double) testData.size();
    results.k = (int)kAverage;
    
    return results; 
  }

  public static Result test5(List<DataPoint> trainingData, List<DataPoint> testData, Integer k) {
    Result results = new Result();
    int error = 0;
    
    results.percentError = (double) error / (double) testData.size();
    results.k = k;
    
    return results; 
  }


  
  /**
   * A result ADT for the test results to make formatting output easier.
   * @author Robert Puckett
   *
   */
  public static class Result {
    double percentError;
    int k;
    double confidence = 0.0;
    
    public String toString() {
      String output = percentError + " " + k;
      if (confidence > 0.0) {
        output = output + " " + confidence;
      }
      return output;
    }
  }
}
